package com.chatplus.application.service.verification;

import cn.hutool.core.lang.Validator;
import cn.hutool.core.util.PhoneUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.chatplus.application.common.constant.RedisPrefix;
import com.chatplus.application.common.exception.BadRequestException;
import com.chatplus.application.common.logging.SouthernQuietLogger;
import com.chatplus.application.common.logging.SouthernQuietLoggerFactory;
import com.chatplus.application.service.verification.domain.model.CaptchaSendInfo;
import com.chatplus.application.service.verification.domain.model.VerificationErrorCode;
import com.chatplus.application.service.verification.impl.EmailCaptchaServiceImpl;
import com.chatplus.application.service.verification.impl.SmsCaptchaServiceImpl;
import org.redisson.api.RList;
import org.redisson.api.RedissonClient;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Objects;

public abstract class PlusCaptchaService {
    static final SouthernQuietLogger LOGGER = SouthernQuietLoggerFactory.getLogger(PlusCaptchaService.class);

    /**
     * 验证码发送频率限制
     */
    private Duration getCaptchaSendLimit() {
        return Duration.ofSeconds(50);
    }

    /**
     * 验证码有效期
     */
    public Duration getCaptchaValidateTime() {
        return Duration.ofSeconds(300);
    }

    private final RedissonClient redissonClient;

    protected PlusCaptchaService(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }

    public abstract boolean sendNotice(String receiver, String noticeMsg);


    public static PlusCaptchaService getInstance(String receiver) {
        boolean isPhone = PhoneUtil.isPhone(receiver);
        if (isPhone) {
            return SpringUtil.getBean(SmsCaptchaServiceImpl.class);
        }
        boolean isEmail = Validator.isEmail(receiver);
        if (isEmail) {
            return SpringUtil.getBean(EmailCaptchaServiceImpl.class);
        }
        throw new BadRequestException("未实现方式");
    }

    public boolean sendCaptcha(String receiver) {
        RList<CaptchaSendInfo> captchaSendInfos = redissonClient.getList(RedisPrefix.USER_LOGIN_SMS_CAPTCHA_PREFIX + receiver);
        CaptchaSendInfo head = captchaSendInfos.get(0);
        if (captchaSendInfos.isExists() && head != null && !head.captchaIsExpire(getCaptchaSendLimit().toSeconds())) {
            throw VerificationErrorCode.SEND_SMS_CAPTCHA_OVER_LIMIT.get();
        }
        String captcha = RandomUtil.randomNumbers(4);
        LOGGER.message("本次发送验证码").context("receiver", receiver).context("captcha", captcha).info();
        boolean success = doSendCaptcha(receiver, captcha);
        if (success) {
            CaptchaSendInfo toSendSmsCaptchaInfo = new CaptchaSendInfo();
            toSendSmsCaptchaInfo.setSuccess(true);
            toSendSmsCaptchaInfo.setCaptcha(captcha);
            toSendSmsCaptchaInfo.setHasVerified(false);
            toSendSmsCaptchaInfo.setSendDateTime(LocalDateTime.now());
            //在队头插入
            if (head == null) {
                captchaSendInfos.add(toSendSmsCaptchaInfo);
            } else {
                captchaSendInfos.addBefore(captchaSendInfos.getFirst(), toSendSmsCaptchaInfo);
            }
            captchaSendInfos.expire(getCaptchaValidateTime());
        } else {
            LOGGER.message("验证码发送失败")
                    .context("receiver", receiver)
                    .warnAsync();
            throw new BadRequestException("验证码发送失败-请联系管理员");
        }
        return true;
    }

    protected abstract boolean doSendCaptcha(String receiver, String captcha);

    /**
     * 验证发送的验证码
     *
     * @param receiver 接收者
     * @param captcha  验证码
     * @return 正确返回true，反之返回false
     */
    public boolean checkCaptcha(String receiver, String captcha) {
        RList<CaptchaSendInfo> captchaSendInfos = redissonClient.getList(RedisPrefix.USER_LOGIN_SMS_CAPTCHA_PREFIX + receiver);
        CaptchaSendInfo head = captchaSendInfos.get(0);
        // 如果已经验证过的话验证码不能再验证，验证码只能使用一次
        if (captchaSendInfos.isExists() && head != null && Objects.equals(false, head.getHasVerified())) {
            boolean matches = Objects.equals(captcha, head.getCaptcha());
            if (matches) {
                captchaSendInfos.replaceAll(captchaSendInfo -> {
                    captchaSendInfo.setHasVerified(true);
                    return captchaSendInfo;
                });
            }
            return matches;
        }
        return false;
    }
}
